package com.easylinkin.linkappapi.common.utils.codegenerator;


import com.easylinkin.linkappapi.common.utils.codegenerator.constant.CodeGeneratorConstant;
import com.google.common.base.CaseFormat;
import freemarker.template.TemplateExceptionHandler;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.*;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

import static com.easylinkin.linkappapi.common.utils.codegenerator.Config.*;
import static com.easylinkin.linkappapi.common.utils.codegenerator.constant.CodeGeneratorConstant.*;


/**
 * 代码生成器，根据数据表名称生成对应的Model、Mapper、Service、Controller简化开发。
 */
public class CodeGenerator {
    //JDBC配置，请修改为你项目的实际配置
    // MySQL不支持schema或者catalog, 需要在JDBC URL中添加nullCatalogMeansCurrent=true。
    // 这样可以避免代码生成器将不同数据库中名字相同的表都生成实体类。


    // @author
    private static final String AUTHOR = "CodeGenerator";
    // @date
    private static final String DATE = new SimpleDateFormat("yyyy/MM/dd").format(new Date());

    public static void main(String[] args) {
        genCodeByCustomModelName(Config.TABLE_NAME, Config.MODEL_NAME);

//        genCode("jgl_plant_two");
//        genModelAndMapper("jgl_plant_two", "JglPlantTwo");
//        genService("disclaimer_info", "DisclaimerInfo");
//        genController("disclaimer_info", "DisclaimerInfo");
    }

    /**
     * 通过数据表名称生成代码，Model 名称通过解析数据表名称获得，下划线转大驼峰的形式。
     * 如输入表名称 "t_user_detail" 将生成 TUserDetail、TUserDetailMapper、TUserDetailService ...
     *
     * @param tableNames 数据表名称...
     */
    public static void genCode(String... tableNames) {
        for (String tableName : tableNames) {
            genCodeByCustomModelName(tableName, null);
        }
    }

    /**
     * 通过数据表名称，和自定义的 Model 名称生成代码
     * 如输入表名称 "t_user_detail" 和自定义的 Model 名称 "User" 将生成 User、UserMapper、UserService ...
     *
     * @param tableName 数据表名称
     * @param modelName 自定义的 Model 名称
     */
    public static void genCodeByCustomModelName(String tableName, String modelName) {
        genModelAndMapper(tableName, modelName);
        genService(tableName, modelName);
        genController(tableName, modelName);
        overwriteModelAndMapperXml(tableName, modelName);
    }

    /**
     * 重写 Entity 和 Mapper.xml
     *
     * @param tableName
     * @param modelName
     */
    private static void overwriteModelAndMapperXml(String tableName, String modelName) {
        String filePath = PROJECT_PATH + JAVA_PATH + PACKAGE_ENTITY_PACKAGE + modelName + ".java";

        File file = new File(filePath);
        List<String> list = null;
        try {
            list = FileUtils.readLines(file, "UTF-8");
            for (int i = 0; i < list.size(); i++) {
                String temp = list.get(i)
                        .replaceAll("import javax\\.persistence.*",
                                "import com.baomidou.mybatisplus.annotation.IdType;\n" +
                                        "import com.baomidou.mybatisplus.annotation.TableId;\n" +
                                        "import com.baomidou.mybatisplus.annotation.TableName;\n" +
                                        "import com.baomidou.mybatisplus.extension.activerecord.Model;\n" +
                                        "import lombok.Data;\n" +
                                        "import lombok.EqualsAndHashCode;\n" +
                                        "import lombok.experimental.Accessors;\n")
                        .replaceAll("@Table\\(name = ", "@Data\n" +
                                "@EqualsAndHashCode(callSuper = false)\n" +
                                "@Accessors(chain = true)\n" + "@TableName\\(")
                        .replaceAll("public class " + modelName, "public class " + modelName + " extends Model<" + modelName + ">")
                        .replaceAll("@Id", "@TableId(type = IdType.AUTO)")
                        .replaceAll("@GeneratedValue.*", "")
                        .replaceAll("@Column\\(.*", "");
                list.remove(i);
                list.add(i, temp);
            }
            FileUtils.writeLines(file, "UTF-8", list, IOUtils.LINE_SEPARATOR_WINDOWS);
        } catch (IOException e) {
            e.printStackTrace();
        }

        overwriteMapperXml(tableName, modelName);
    }

    private static void overwriteMapperXml(String tableName, String modelName) {
        String filePath = PROJECT_PATH + RESOURCES_PATH + CodeGeneratorConstant.packageConvertPath(MAPPER_XML_PATH) + modelName + "Mapper.xml";
        Map<String, Object> param = getParamMap(tableName, modelName);
        String modelNameLowerCamel = (String) param.get("modelNameLowerCamel");
        File file = new File(filePath);
        List<String> list = null;
        try {
            list = FileUtils.readLines(file, "UTF-8");
            for (int i = 0; i < list.size(); i++) {
                String temp = list.get(i)
                        .replaceAll("</mapper>",
                                "\n" +
                                        "    <select id=\"selectPage\" resultMap=\"BaseResultMap\">\n" +
                                        "        select * from " + tableName + " where tenant_id = #{" + modelNameLowerCamel + ".tenantId} order by modify_time desc, create_time desc" + "\n" +
                                        "    </select>\n" +
                                        "\n" +
                                        "    <select id=\"getOneById\" resultMap=\"BaseResultMap\">\n" +
                                        "        select * from " + tableName + " where id = #{id}\n" +
                                        "    </select>");
                list.remove(i);
                list.add(i, temp);
            }
            list.add("\n");
            list.add("</mapper>");
            FileUtils.writeLines(file, "UTF-8", list, IOUtils.LINE_SEPARATOR_WINDOWS);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static void genModelAndMapper(String tableName, String modelName) {
        Context context = new Context(ModelType.FLAT);
        context.setId("Potato");
        context.setTargetRuntime("MyBatis3Simple");
        context.addProperty(PropertyRegistry.CONTEXT_BEGINNING_DELIMITER, "`");
        context.addProperty(PropertyRegistry.CONTEXT_ENDING_DELIMITER, "`");

        JDBCConnectionConfiguration jdbcConnectionConfiguration = new JDBCConnectionConfiguration();
        jdbcConnectionConfiguration.setConnectionURL(JDBC_URL);
        jdbcConnectionConfiguration.setUserId(JDBC_USERNAME);
        jdbcConnectionConfiguration.setPassword(JDBC_PASSWORD);
        jdbcConnectionConfiguration.setDriverClass(JDBC_DIVER_CLASS_NAME);
        context.setJdbcConnectionConfiguration(jdbcConnectionConfiguration);

        PluginConfiguration pluginConfiguration = new PluginConfiguration();
        pluginConfiguration.setConfigurationType("tk.mybatis.mapper.generator.MapperPlugin");
        pluginConfiguration.addProperty("mappers", MAPPER_INTERFACE_REFERENCE);
        context.addPluginConfiguration(pluginConfiguration);

        JavaModelGeneratorConfiguration javaModelGeneratorConfiguration = new JavaModelGeneratorConfiguration();
        javaModelGeneratorConfiguration.setTargetProject(PROJECT_PATH + JAVA_PATH);
        javaModelGeneratorConfiguration.setTargetPackage(ENTITY_PACKAGE);
        context.setJavaModelGeneratorConfiguration(javaModelGeneratorConfiguration);

        SqlMapGeneratorConfiguration sqlMapGeneratorConfiguration = new SqlMapGeneratorConfiguration();
        sqlMapGeneratorConfiguration.setTargetProject(PROJECT_PATH + RESOURCES_PATH);
//        sqlMapGeneratorConfiguration.setTargetPackage("mapper");
        sqlMapGeneratorConfiguration.setTargetPackage(MAPPER_XML_PATH);
        context.setSqlMapGeneratorConfiguration(sqlMapGeneratorConfiguration);

        JavaClientGeneratorConfiguration javaClientGeneratorConfiguration = new JavaClientGeneratorConfiguration();
        javaClientGeneratorConfiguration.setTargetProject(PROJECT_PATH + JAVA_PATH);
        javaClientGeneratorConfiguration.setTargetPackage(MAPPER_PACKAGE);
        javaClientGeneratorConfiguration.setConfigurationType("XMLMAPPER");
        context.setJavaClientGeneratorConfiguration(javaClientGeneratorConfiguration);

        TableConfiguration tableConfiguration = new TableConfiguration(context);
        tableConfiguration.setTableName(tableName);
        if (StringUtils.isNotEmpty(modelName)) {
            tableConfiguration.setDomainObjectName(modelName);
        }
        tableConfiguration.setGeneratedKey(new GeneratedKey("id", "Mysql", true, null));
        context.addTableConfiguration(tableConfiguration);

        List<String> warnings;
        MyBatisGenerator generator;
        try {
            Configuration config = new Configuration();
            config.addContext(context);
            config.validate();

            boolean overwrite = true;
            DefaultShellCallback callback = new DefaultShellCallback(overwrite);
            warnings = new ArrayList<String>();
            generator = new MyBatisGenerator(config, callback, warnings);
            generator.generate(null);
        } catch (Exception e) {
            throw new RuntimeException("生成Model和Mapper失败", e);
        }

        if (generator.getGeneratedJavaFiles().isEmpty() || generator.getGeneratedXmlFiles().isEmpty()) {
            throw new RuntimeException("生成Model和Mapper失败：" + warnings);
        }
        if (StringUtils.isEmpty(modelName)) {
            modelName = tableNameConvertUpperCamel(tableName);
        }
        System.out.println(modelName + ".java 生成成功");
//        System.out.println(modelName + "Mapper.java 生成成功");
        System.out.println(modelName + "Mapper.xml 生成成功");
    }

    public static void genService(String tableName, String modelName) {
        try {
            freemarker.template.Configuration cfg = getConfiguration();

            Map<String, Object> data = getParamMap(tableName, modelName);

            String modelNameUpperCamel = (String) data.get("modelNameUpperCamel");

            // 生成Mapper.java
            File file4 = new File(PROJECT_PATH + JAVA_PATH + PACKAGE_MAPPER_PACKAGE + modelNameUpperCamel + "Mapper.java");
            if (!file4.getParentFile().exists()) {
                file4.getParentFile().mkdirs();
            }
            cfg.getTemplate("mapper-java.ftl").process(data,
                    new FileWriter(file4));
            System.out.println(modelNameUpperCamel + "Mapper.java 生成成功");


            // 生成导出表格的vo
            File file3 = new File(PROJECT_PATH + JAVA_PATH + PACKAGE_ENTITY_PACKAGE + "/vo/" + modelNameUpperCamel + "Vo.java");
            if (!file3.getParentFile().exists()) {
                file3.getParentFile().mkdirs();
            }
            cfg.getTemplate("entity-vo.ftl").process(data,
                    new FileWriter(file3));
            System.out.println(modelNameUpperCamel + "Vo.java 生成成功");

//            生成service
            File file = new File(PROJECT_PATH + JAVA_PATH + PACKAGE_PATH_SERVICE + modelNameUpperCamel + "Service.java");
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            cfg.getTemplate("service.ftl").process(data,
                    new FileWriter(file));
            System.out.println(modelNameUpperCamel + "Service.java 生成成功");

//            生成serviceImpl
            File file1 = new File(PROJECT_PATH + JAVA_PATH + PACKAGE_PATH_SERVICE_IMPL + modelNameUpperCamel + "ServiceImpl.java");
            if (!file1.getParentFile().exists()) {
                file1.getParentFile().mkdirs();
            }
            cfg.getTemplate("service-impl.ftl").process(data,
                    new FileWriter(file1));
            System.out.println(modelNameUpperCamel + "ServiceImpl.java 生成成功");

        } catch (Exception e) {
            throw new RuntimeException("生成Service失败", e);
        }
    }

    private static Map<String, Object> getParamMap(String tableName, String modelName) {
        Map<String, Object> data = new HashMap<>();
        data.put("date", DATE);
        data.put("author", AUTHOR);
        String modelNameUpperCamel = StringUtils.isEmpty(modelName) ? tableNameConvertUpperCamel(tableName) : modelName;
        data.put("modelNameUpperCamel", modelNameUpperCamel);
        data.put("modelNameLowerCamel", tableNameConvertLowerCamel(tableName));
        data.put("basePackage", BASE_PACKAGE);
        return data;
    }

    /**
     * 基础生成controller
     *
     * @param tableName 表名
     * @param modelName 实体名
     */
    public static void genController(String tableName, String modelName) {
        try {
            freemarker.template.Configuration cfg = getConfiguration();

            Map<String, Object> data = getParamMap(tableName, modelName);
            String modelNameUpperCamel = (String) data.get("modelNameUpperCamel");

            File file = new File(PROJECT_PATH + JAVA_PATH + PACKAGE_PATH_CONTROLLER + modelNameUpperCamel + "Controller.java");
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            cfg.getTemplate("controller-restful.ftl").process(data, new FileWriter(file));
            System.out.println(modelNameUpperCamel + "Controller.java 生成成功");
        } catch (Exception e) {
            throw new RuntimeException("生成Controller失败", e);
        }

    }


    private static freemarker.template.Configuration getConfiguration() throws IOException {
        freemarker.template.Configuration cfg = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_23);
        cfg.setDirectoryForTemplateLoading(new File(TEMPLATE_FILE_PATH));
        cfg.setDefaultEncoding("UTF-8");
        cfg.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
        return cfg;
    }

    private static String tableNameConvertLowerCamel(String tableName) {
        return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, tableName.toLowerCase());
    }

    private static String tableNameConvertUpperCamel(String tableName) {
        return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, tableName.toLowerCase());

    }

    private static String tableNameConvertMappingPath(String tableName) {
        // 兼容使用大写的表名
        tableName = tableName.toLowerCase();
        return "/" + (tableName.contains("_") ? tableName.replaceAll("_", "/") : tableName);
    }

    private static String modelNameConvertMappingPath(String modelName) {
        String tableName = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, modelName);
        return tableNameConvertMappingPath(tableName);
    }


}
